﻿// Copyright (c) MichaConrad. All rights reserved. See License.txt in the project root for license information.

namespace LinqToLdapExtensions.Scope
{
    using System;
    using System.Collections.Generic;
    using LinqToLdap;

    /// <summary>
    /// Base implementation which defines the contract for implementations which know how to
    /// scope the notion of a <see cref="IDirectoryContextFactory.GetCurrentContext()">current session</see>.
    /// This base class also provides static methods for bind/unbind.
    /// <br/>
    /// The notion of a contextual session is managed by some external entity
    /// (generally some form of interceptor like the HttpModule).<br/>
    /// This external manager is responsible for scoping these contextual sessions
    /// appropriately binding/unbinding them here for exposure to the application
    /// through <see cref="IDirectoryContextFactory.GetCurrentContext()"/> calls.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Implementations should adhere to the following:
    /// <list type="bullet">
    /// <item><description>contain a constructor accepting a single argument of type
    /// <see cref="IDirectoryContextFactory" /></description></item>
    /// <item><description>should be thread safe</description></item>
    /// </list>
    /// </para>
    /// </remarks>
    /// <seealso cref="IDirectoryContextFactory"/>
    public abstract class CurrentDirectoryContext : ICurrentDirectoryContextBinding
    {
        /// <summary>
        /// The directory context factory.
        /// </summary>
        private readonly IDirectoryContextFactory directoryContextFactory;

        /// <summary>
        /// Initializes a new instance of the <see cref="CurrentDirectoryContext"/> class.
        /// </summary>
        /// <param name="directoryContextFactory">
        /// The directory context factory.
        /// </param>
        protected CurrentDirectoryContext(IDirectoryContextFactory directoryContextFactory)
        {
            this.directoryContextFactory = directoryContextFactory;
        }

        /// <summary>
        /// Gets the current <see cref="IDirectoryContext"/> according to the scoping defined
        /// by this implementation.
        /// </summary>
        /// <remarks>
        /// This getter will fail if there is no <see cref="IDirectoryContext"/> bound to the store.
        /// </remarks>
        public virtual IDirectoryContext CurrentContext
        {
            get
            {
                return this.Store[this.directoryContextFactory];
            }
        }

        /// <summary>
        /// Gets the dictionary to store the <see cref="IDirectoryContext"/>.
        /// </summary>
        /// <remarks>
        /// The concrete implementation must override this and the backing field should
        /// reflect the correct scope, e.g. using <see cref="ThreadStaticAttribute"/> on the field could be used for thread static scope.
        /// </remarks>
        protected abstract IDictionary<IDirectoryContextFactory, IDirectoryContext> Store { get; }

        /// <summary>
        /// Returns whether there is a <see cref="LinqToLdap.IDirectoryContext"/> bound to the <see cref="CurrentDirectoryContext"/>.
        /// </summary>
        /// <param name="factory">
        /// The factory used for binding.
        /// </param>
        /// <returns>
        /// <c>True</c> whether there is a <see cref="LinqToLdap.IDirectoryContext"/> 
        /// bound to the <see cref="CurrentDirectoryContext"/>, otherwise <c>False</c>.
        /// </returns>
        public static bool HasBind(IDirectoryContextFactory factory)
        {
            return factory.CurrentDirectoryContextBinding.HasBind();
        }

        /// <summary>
        /// Binds the <see cref="LinqToLdap.IDirectoryContext"/> to the <see cref="CurrentDirectoryContext"/> scope.
        /// </summary>
        /// <param name="factory">
        /// The factory used for binding.
        /// </param>
        /// <param name="context">
        /// The <see cref="LinqToLdap.IDirectoryContext"/> which should be bound to the scope.
        /// </param>
        public static void Bind(IDirectoryContextFactory factory, IDirectoryContext context)
        {
            factory.CurrentDirectoryContextBinding.Bind(context);
        }

        /// <summary>
        /// Unbinds the <see cref="LinqToLdap.IDirectoryContext"/> from the <see cref="CurrentDirectoryContext"/> scope.
        /// </summary>
        /// <param name="factory">
        /// The factory used for binding.
        /// </param>
        /// <returns>
        /// The <see cref="IDirectoryContext"/> which was bound.
        /// </returns>
        public static IDirectoryContext Unbind(IDirectoryContextFactory factory)
        {
            return factory.CurrentDirectoryContextBinding.Unbind();
        }

        /// <summary>
        /// Returns whether there is a <see cref="LinqToLdap.IDirectoryContext"/> bound to the <see cref="CurrentDirectoryContext"/>.
        /// </summary>
        /// <returns>
        /// <c>True</c> whether there is a <see cref="LinqToLdap.IDirectoryContext"/> 
        /// bound to the <see cref="CurrentDirectoryContext"/>, otherwise <c>False</c>.
        /// </returns>
        public bool HasBind()
        {
            return this.Store.ContainsKey(this.directoryContextFactory);
        }

        /// <summary>
        /// Binds the <see cref="LinqToLdap.IDirectoryContext"/> to the <see cref="CurrentDirectoryContext"/>.
        /// </summary>
        /// <param name="context">
        /// The <see cref="LinqToLdap.IDirectoryContext"/> which should be bound to the scope.
        /// </param>
        public void Bind(IDirectoryContext context)
        {
            this.Store.Add(this.directoryContextFactory, context);
        }

        /// <summary>
        /// Unbinds the <see cref="LinqToLdap.IDirectoryContext"/> from the <see cref="CurrentDirectoryContext"/> scope.
        /// </summary>
        /// <returns>
        /// The <see cref="IDirectoryContext"/> which was bound.
        /// </returns>
        public IDirectoryContext Unbind()
        {
            var ctx = this.Store[this.directoryContextFactory];
            this.Store.Remove(this.directoryContextFactory);
            return ctx;
        }
    }
}
